﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using NetPong;
using NetPong.Networking;

namespace Netpong.Networking
{
    public delegate void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e);
    public delegate void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e);

    public class UpdateHostEventArgs : EventArgs
    {
        public GameTime gameTime;

        public UpdateHostEventArgs(GameTime gt)
        {
            gameTime = gt;
        }
    }

    public class PacketReceivedEventArgs : EventArgs
    {
        public PacketReader packetReader;

        public PacketReceivedEventArgs(PacketReader pr)
        { packetReader = pr; }
    }

    public class NetworkManager
    {
        public event EventHandler<UpdateHostEventArgs> UpdateHostEvent;
        //public event EventHandler<PacketReceivedEventArgs> PacketReceived;
        public event EventHandler UpdateClientEvent;
        public ITransmittableFactory TransmittableFactory;
        public bool IsHost
        {
            get { return networkSession.IsHost; }
        }
        public int CountOfGamers
        {
            get { return networkSession.AllGamers.Count; }
        }

        private NetworkSession networkSession;
        private LocalNetworkGamer localNetworkGamer0 { get { return networkSession.LocalGamers[0]; } }
        //the key is the network object's id; which will be the same on all machines
        private Dictionary<int,ITransmittable> networkObjects = new Dictionary<int,ITransmittable>();
        private static int objCount = 0;

        public NetworkManager(NetworkSession net, ITransmittableFactory factory)
        {
            TransmittableFactory = factory;
            networkSession = net;
            UpdateHostEvent += new EventHandler<UpdateHostEventArgs>(NetworkManager_UpdateClients);
            networkSession.SessionEnded += delegate(object sender, NetworkSessionEndedEventArgs e)
            {
                networkSession.Dispose();
                networkSession = null;
            };
        }

        public NetworkGamer Host
        {
            get { return networkSession.Host; }
        }

        /// <summary>
        /// this method is used by the server when a new network object needs to be created
        /// </summary>
        /// <param name="t">the network object</param>
        /// <returns>the key of the network object or -1 if the machine is not the host</returns>
        public int CreateNetworkObj(ITransmittable t)
        {
            if (networkSession.IsHost)
            {
                networkObjects.Add(++objCount, t);
                return objCount;
            }
            return -1;
        }

        public void AddNetworkObj(int key, ITransmittable t)
        {
            networkObjects.Add(key, t);
        }

        /// <summary>
        /// if your machine is the game host send a packet to a specific gamer.
        /// </summary>
        /// <param name="pw"></param>
        /// <param name="gamer"></param>
        public void WriteToNetwork(PacketWriter pw, NetworkGamer gamer)
        {
            if (!networkSession.IsHost)
                return;

            //assumes LocalGamer[0] on Host machine is Host Gamer
            networkSession.LocalGamers[0].SendData(pw, SendDataOptions.Reliable, gamer);
        }

        //push all networkObjects to clients
        void NetworkManager_UpdateClients(object sender, UpdateHostEventArgs e)
        {
            foreach (ITransmittable t in networkObjects.Values)                   
            {
                localNetworkGamer0.SendData(  t.WriteToNetwork(), SendDataOptions.InOrder);
            }
        }

        public void AddGamerJoinedHook(GamerJoinedEventHandler gj)
        {
            networkSession.GamerJoined += new EventHandler<GamerJoinedEventArgs>(gj);
        }

        public void AddSessionEndedHook(SessionEndedEventHandler se)
        {
            networkSession.SessionEnded += new EventHandler<NetworkSessionEndedEventArgs>(se);
        }

        public void UpdateNetwork(GameTime gt)
        {
            //triggers the appropriate update events
            if (networkSession.IsHost)
            {
                //copy to temp for thread safety
                EventHandler<UpdateHostEventArgs> temp = UpdateHostEvent;
                if (temp != null)
                    temp(this, new UpdateHostEventArgs(gt));
                    //GameTime is needed to update Phys Engine
            }
            else 
            {
                EventHandler temp = UpdateClientEvent;
                if (temp != null)
                    temp(this, null);
            }

            //while there are packets on the network, process then
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                while (gamer.IsDataAvailable)
                {
                    NetworkGamer sender = null;
                    PacketReader packetReader = new PacketReader();
                    // Read a single packet from the network.
                    gamer.ReceiveData(packetReader, out sender);

                    ITransmittable t = null;
                    int key = packetReader.ReadInt32();
                    networkObjects.TryGetValue(key, out t);

                    //if the packet is referencing a known network object, update it
                    if (t != null)
                    {
                        t.UpdateFromNetwork(packetReader);
                    }
                    else//create the ITransmittable and add it to networkObjects
                    {
                        networkObjects.Add(key,TransmittableFactory.CreateFromPacket(packetReader));
                    }
                }
            }

            // Pump the underlying session object.
            networkSession.Update();
        }
    }
}
